/*
    nspire-gamekit - a simple homebrew game framework
    Copyright (C) 2011  Daniel Tang

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <os.h>
#include "screen.h"
#include "autorelease.h"
#include "optimized.h"
#include "types/ngk.h"

#define SCREEN_BASE_ADDR	0xa4000100
#define SCREEN_SIZE			0x95ff
#define MIN(x,y)            ((x) < (y) ? (x) : (y))
#define BUFFER_SIZE(screen) ((screen)->size.width*(screen)->size.height)

void screenBufferAlphaMemCpy(void* dest, void* src, uint32_t size);

//Be very careful when modifying this structure
struct NGKScreenBufferPriv {
    void* buffer;
    NGKSize size;
};

uint16_t screenBufferWidth(const NGKScreenBuffer* screen) {
    return screen->size.width;
}
uint16_t screenBufferHeight(const NGKScreenBuffer* screen) {
    return screen->size.height;
}
void* screenBufferData(const NGKScreenBuffer* screen) {
    return screen->buffer;
}


NGKScreenBuffer* screenBufferAlloc(NGKSize size) {
    NGKScreenBuffer* screen = NULL;
    screen = gameAutoMalloc(sizeof(NGKScreenBuffer));
    if (!screen) return NULL;
    
    screen->size = size;
    screen->buffer = gameAutoMalloc(size.height * size.width);
    
    if (!screen->buffer) {
        gameAutoFree(screen);
        return NULL;
    }
    
    return screen;
}

NGKScreenBuffer* screenBufferFromCopy(const NGKScreenBuffer* old) {
    NGKScreenBuffer* new = screenBufferAlloc(NGKSizeMake(old->size.width, old->size.height));
    if (new) {
        memcpy(new->buffer, old->buffer, BUFFER_SIZE(old));
    }
    return new;
}

void screenBufferDestroy(NGKScreenBuffer* screen) {
    if (screen) {
        if (screen->buffer) gameAutoFree(screen->buffer);
        gameAutoFree(screen);
    }
}

void screenBufferDrawScreen(NGKPoint point, NGKScreenBuffer* src, NGKScreenBuffer* dest) {
    uint16_t i;
    NGKPosInt x = point.x, y = point.y;
    NGKRect draw_region;
    char *srcBuffer = src->buffer;
    char *destBuffer = dest->buffer;
    
    if (x >= dest->size.width) return;
    if (y >= dest->size.height) return;
    if (x <= -src->size.width) return;
    if (y <= -src->size.height) return;
    
    //Find the common parts and work out a rectangle in which to draw things
    //Draw region is relative to src
    draw_region.position.x = (x < 0) ? x*-1 : 0;
    draw_region.position.y = (y < 0) ? y*-1 : 0;
    
    draw_region.size.width = (x < 0) ? MIN((src->size.width - (x*-1)), dest->size.width) : MIN(src->size.width, (dest->size.width)-x);
    draw_region.size.height = (y < 0) ? MIN((src->size.height - (y*-1)), dest->size.height) : MIN(src->size.height, (dest->size.height)-y);
    
    x = (x < 0) ? 0 : x;
    y = (y < 0) ? 0 : y;
    
    
    srcBuffer  += draw_region.position.x + (draw_region.position.y * src->size.width);
    destBuffer += x + (y * dest->size.width);
    
    //We might be able to optimize by performing direct operations
    //instead of using screenBufferGet/SetPixel()
    for (i=0;i<draw_region.size.height;i++) {
        screenBufferAlphaMemCpy(destBuffer, srcBuffer, draw_region.size.width);
        srcBuffer += src->size.width;
        destBuffer += dest->size.width;
    }
}

void screenBufferClear(char color, NGKScreenBuffer* screen)
{
	memset(screen->buffer, color, BUFFER_SIZE(screen));
}

char screenBufferGetPixel(NGKPoint point, NGKScreenBuffer* screen)
{
	char *buffer = (char*)screen->buffer;
	NGKPosInt x = point.x, y = point.y;
	if (y >= (screen->size.height) || y < 0) return 0;
	if (x >= (screen->size.width) || x < 0) return 0;
	buffer += y*screen->size.width;
	buffer += x;

	return *buffer;
}

void screenBufferSetPixel(NGKPoint point, char color, NGKScreenBuffer* screen)
{
	char *buffer = (char*)screen->buffer;
	NGKPosInt x = point.x, y = point.y;
	
	if (y >= screen->size.height || y < 0) return;
	if (x >= screen->size.width || x < 0) return;
	if (screenBufferColorGetAlphaValue(color) == 0) return; //Needs alpha blending
	
	buffer += y*screen->size.width;
	buffer += x;
	*buffer = color;
}

#define COMBINE_PIXELS(x, y) ( ((y) & 0b00001111)  | ((x)<<4) )
//Let's rewrite this in assembly
void screenBufferFlushToScreen(NGKScreenBuffer* screen) {
    int i;
    if (screen->size.width != SCREEN_WIDTH || screen->size.height != SCREEN_HEIGHT) return;
    char (*buffer)[2] = screen->buffer;
    char *realScreen = (char*)SCREEN_BASE_ADDR;
    
    for (i=0; i<SCREEN_SIZE; i++) {
        *realScreen = COMBINE_PIXELS(buffer[i][0], buffer[i][1]);
        realScreen++;
    }
}
